package com.wechat.service;

import com.github.binarywang.wxpay.bean.entpay.EntPayRequest;
import com.github.binarywang.wxpay.bean.entpay.EntPayResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPaySendRedpackRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.EntPayService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import com.wechat.entity.Question;
import com.wechat.entity.User;
import com.wechat.entity.UserBalanceRecord;
import com.wechat.entity.UserRechargeRecord;
import org.apache.catalina.servlet4preview.http.HttpServletRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author cgc
 * @date 2018/4/2
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class PayService {

    @Resource(name = "wxPayService")
    private WxPayService wxPayService;

    @Value("${wx.clientIp:115.236.20.69}")
    private String clientIp;
    @Value("${wx.notifyUrl:https://testminiapp.yuntask.com/api/v1/pay/notify}")
    private String notifyUrl;
    /**
     * 微信费率 一万分之几
     */
    @Value("${wxpay.rate:200}")
    private int wxPayRate;

    @Autowired
    private UserService userService;
    @Autowired
    private UserRechargeService userRechargeService;
    @Autowired
    private UserBalanceService userBalanceService;
    @Autowired
    private QuestionService questionService;


    /**
     * 微信下单
     *
     * @param user
     * @param amount
     * @param questionId 如果有的话说明是设置红包下单
     * @param request
     * @return
     * @throws WxPayException
     */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> unifiedOrder(User user, BigDecimal amount, Integer questionId, HttpServletRequest request) throws WxPayException {
        String outTradeNo = "";
        String body = "杭州志卓科技股份有限公司-红包金额";

        // TODO: 2018/4/2 根据类型获取商品描述 body

        String ip = this.getIpAddr(request);

        BigDecimal taxesFee = amount.multiply(new BigDecimal ((double)wxPayRate/(double)10000)).setScale(2,BigDecimal.ROUND_UP);
        BigDecimal realFee = amount.add(taxesFee);

        UserRechargeRecord rechargeRecord = new UserRechargeRecord();
        rechargeRecord.setAmount(amount);
        rechargeRecord.setTaxesFee(taxesFee);
        rechargeRecord.setRealFee(realFee);
        rechargeRecord.setBalance(user.getBalance());
        rechargeRecord.setUserId(user.getId());
        userRechargeService.save(rechargeRecord);
        if (questionId != null && questionId > 0) {
            Question question = new Question();
            question.setId(questionId);
            question.setRechargeId(rechargeRecord.getId());
            questionService.updateQuestion(question);
        }
        outTradeNo = String.valueOf(rechargeRecord.getId());

        return this.payMent(body, outTradeNo, realFee.multiply(new BigDecimal(100)).intValue(), user.getOpenId(), ip);
    }


    private Map<String, String> payMent(String body, String outTradeNo, Integer totalFee, String openId, String ip) throws WxPayException {

        WxPayConfig wxPayConfig = this.wxPayService.getConfig();

        WxPayUnifiedOrderResult resoult = this.miniAppUnifiedOrder(body, outTradeNo, totalFee, ip, notifyUrl, openId);
        if (resoult != null && !StringUtils.isEmpty(resoult.getPrepayId())) {
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = DigestUtils.md5Hex(String.valueOf(System.currentTimeMillis()));
            Map<String, String> map = new HashMap<>(5);
            map.put("appId", wxPayConfig.getAppId());
            map.put("timeStamp", timeStamp);
            map.put("nonceStr", nonceStr);
            map.put("package", "prepay_id=" + resoult.getPrepayId());
            map.put("signType", "MD5");

            String paySign = SignUtils.createSign(map, null, wxPayConfig.getMchKey(), false);
            if (!StringUtils.isEmpty(paySign)) {
                Map<String, String> resultMap = new HashMap<>(5);
                resultMap.put("timeStamp", timeStamp);
                resultMap.put("nonceStr", nonceStr);
                resultMap.put("package", "prepay_id=" + resoult.getPrepayId());
                resultMap.put("signType", "MD5");
                resultMap.put("paySign", paySign);
                return resultMap;
            }
        }

        return null;
    }

    /**
     * 微信支付回调处理
     *
     * @param notify
     * @return
     * @throws WxPayException
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean notify(String notify) throws WxPayException {

        WxPayOrderNotifyResult result = wxPayService.parseOrderNotifyResult(notify);

        Long rechargeId = Long.valueOf(result.getOutTradeNo());
        String openId = result.getOpenid();
        User user = userService.findByOpenId(openId);
        UserRechargeRecord record = userRechargeService.findById(rechargeId);
        String transactionId = result.getTransactionId();

        //如果支付成功直接返回成功
        if (record.getTradeStatus().equals((byte) 1)) {
            return true;
        }

        Date date = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            date = formatter.parse(result.getTimeEnd());
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return this.recharge(record, user, date, transactionId);
    }


    /**
     *
     * @param record
     * @param user
     * @param date
     * @param transactionId
     * @return
     */
    public Boolean recharge(UserRechargeRecord record, User user, Date date, String transactionId) {

        if (user.getId().equals(record.getUserId())) {

            if (record.getRechargeType() == UserRechargeRecord.RECHARGE_TYPE_REDPACKETIN) {

                //更新充值记录
                UserRechargeRecord record1 = new UserRechargeRecord();
                record1.setId(record.getId());
                record1.setTradeNo(transactionId);
                record1.setDateTraded(date);
                record1.setTradeStatus((byte) 1);
                userRechargeService.update(record1);
                //更新问题记录
                Question question = questionService.findQuestionByRechargeId(record.getId());
                Question question1 = new Question();
                question1.setId(question.getId());
                question1.setRechargeId(record.getId());
                question1.setAmountStatus(Question.AMOUNT_STATUS_NOT_TAKE);
                question1.setTradeStatus((short) 1);
                question1.setAccountAmount(record.getAmount());
                questionService.updateQuestion(question1);
                //增加账户财务流水
                UserBalanceRecord balanceRecord = new UserBalanceRecord();
                balanceRecord.setBalance(user.getBalance());
                balanceRecord.setUserId(user.getId());
                balanceRecord.setAmount(record.getAmount());
                balanceRecord.setTitle("充值-问题红包充值");
                balanceRecord.setRecordType(UserBalanceRecord.RECORD_TYPE_REDPACKETIN);
                balanceRecord.setRechargeId(record.getId());
                userBalanceService.save(balanceRecord);
                UserBalanceRecord balanceRecord1 = new UserBalanceRecord();
                balanceRecord1.setBalance(user.getBalance().add(record.getAmount()));
                balanceRecord1.setUserId(user.getId());
                balanceRecord1.setAmount(record.getAmount().negate());
                balanceRecord1.setTitle("充值-问题红包支出");
                balanceRecord1.setRecordType(UserBalanceRecord.RECORD_TYPE_REDPACKETOUT);
                balanceRecord1.setQuestionId(question.getId());
                userBalanceService.save(balanceRecord1);
                return true;
            } else if (record.getRechargeType() == UserRechargeRecord.RECHARGE_TYPE_ACCOUNT_RECHARGE) {

                //更新充值记录
                UserRechargeRecord record1 = new UserRechargeRecord();
                record1.setId(record.getId());
                record1.setTradeNo(transactionId);
                record1.setDateTraded(date);
                record1.setTradeStatus((byte) 1);
                userRechargeService.update(record1);
                //增加账户财务流水并用户余额
                UserBalanceRecord balanceRecord = new UserBalanceRecord();
                balanceRecord.setBalance(user.getBalance());
                balanceRecord.setUserId(user.getId());
                balanceRecord.setAmount(record.getAmount());
                balanceRecord.setTitle("充值-用户余额充值");
                balanceRecord.setRecordType(UserBalanceRecord.RECORD_TYPE_ACCOUNT_RECHARGE);
                userBalanceService.save(balanceRecord);
                userService.updateBalance(user.getId(), record.getAmount());
                return true;
            }
        }
        return false;
    }

    /**
     * 获取当前网络ip
     *
     * @param request
     * @return
     */
    public String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress = inet.getHostAddress();
            }
        }
        //对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
        //"***.***.***.***".length() = 15
        if (ipAddress != null && ipAddress.length() > 15) {
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }

    /**
     * 微信统一支付
     * 1 为必填
     *
     * @param body           1 商品描述交易字段格式根据不同的应用场景按照以下格式：杭州志卓科技股份有限公司-销售商品类目
     * @param detail         0 商品详细描述，对于使用单品优惠的商户，改字段必须按照规范上传
     * @param attach         0 附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
     * @param outTradeNo     1 商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。
     * @param feeType        0 符合ISO 4217标准的三位字母代码，默认人民币：CNY
     * @param totalFee       1 订单总金额，单位为 分
     * @param spbillCreateIp 1 用户端实际ip
     * @param timeStart      0 订单生成时间，格式为yyyyMMddHHmmss，如2009年12月25日9点10分10秒表示为20091225091010
     * @param timeExpire     0 订单失效时间，格式为yyyyMMddHHmmss，如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的，由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期，所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。
     * @param goodsTag       0 订单优惠标记，代金券或立减优惠功能的参数
     * @param notifyUrl      1 接收微信支付异步通知回调地址，通知url必须为直接可访问的url，不能携带参数。
     * @param tradeType      1 支付类型 JSAPI
     * @param productId      0 rade_type=NATIVE时（即扫码支付），此参数必传。此参数为二维码中包含的商品ID，商户自行定义。
     * @param limitPay       0 no_credit--指定不能使用信用卡支付
     * @param openid         1 trade_type=JSAPI，此参数必传，用户在商户appid下的唯一标识。
     * @param subOpenid      0
     * @param sceneInfo      0
     * @param fingerprint    0
     * @return
     */
    private WxPayUnifiedOrderResult unifiedOrder(String body, String detail, String attach, String outTradeNo, String feeType, Integer totalFee
            , String spbillCreateIp, String timeStart, String timeExpire, String goodsTag, String notifyUrl, String tradeType
            , String productId, String limitPay, String openid, String subOpenid, String sceneInfo, String fingerprint) throws WxPayException {

        WxPayUnifiedOrderRequest wxRequest = new WxPayUnifiedOrderRequest();
        wxRequest.setDeviceInfo("WEB");
        wxRequest.setBody(body);
        wxRequest.setDetail(detail);
        wxRequest.setAttach(attach);
        wxRequest.setOutTradeNo(outTradeNo);
        wxRequest.setFeeType(feeType);
        wxRequest.setTotalFee(totalFee);
        wxRequest.setSpbillCreateIp(spbillCreateIp);
        wxRequest.setTimeStart(timeStart);
        wxRequest.setTimeExpire(timeExpire);
        wxRequest.setGoodsTag(goodsTag);
        wxRequest.setNotifyUrl(notifyUrl);
        wxRequest.setTradeType(tradeType);
        wxRequest.setProductId(productId);
        wxRequest.setLimitPay(limitPay);
        wxRequest.setOpenid(openid);
        wxRequest.setSubOpenid(subOpenid);
        wxRequest.setSceneInfo(sceneInfo);
        wxRequest.setFingerprint(fingerprint);

        return wxPayService.unifiedOrder(wxRequest);
    }


    /**
     * 微信小程序支付
     *
     * @param body           按照以下格式：杭州志卓科技股份有限公司-销售商品类目
     * @param outTradeNo     商户系统内部订单号
     * @param totalFee       订单总金额，单位为 分
     * @param spbillCreateIp 用户端实际ip
     * @param notifyUrl      接收微信支付异步通知回调地址，通知url必须为直接可访问的url，不能携带参数。
     * @param openId         trade_type=JSAPI，此参数必传，用户在商户appid下的唯一标识。
     * @return
     */
    public WxPayUnifiedOrderResult miniAppUnifiedOrder(String body, String outTradeNo, Integer totalFee, String spbillCreateIp
            , String notifyUrl, String openId) throws WxPayException {
        return this.unifiedOrder(body, null, null, outTradeNo, null, totalFee, spbillCreateIp, null, null, null, notifyUrl, "JSAPI"
                , null, null, openId, null, null, null);
    }


    /**
     * 微信企业付款 1 为必填
     *
     * @param mchAppId       1 申请商户号的appid或商户号绑定的appid
     * @param mchId          1 微信支付分配的商户号
     * @param nonceStr       1 随机字符串，不长于32位
     * @param sign           1 签名
     * @param partnerTradeNo 1 商户订单号，需保持唯一性
     * @param openid         1 商户appid下，某用户的openid
     * @param checkName      1 NO_CHECK：不校验真实姓名;FORCE_CHECK：强校验真实姓名
     * @param reUserName     0 收款用户真实姓名。如果check_name设置为FORCE_CHECK，则必填用户真实姓名
     * @param amount         1 企业付款金额，单位为 分
     * @param description    1 企业付款操作说明信息。必填。
     * @param spbillCreateIp 1 该IP同在商户平台设置的IP白名单中的IP没有关联，该IP可传用户端或者服务端的IP。
     * @param deviceInfo     0 微信支付分配的终端设备号
     * @param signType       0 默认MD5
     * @return
     * @throws WxPayException
     */
    private EntPayResult entPay(String mchAppId, String mchId, String nonceStr, String sign, String partnerTradeNo, String openid
            , String checkName, String reUserName, Integer amount, String description, String spbillCreateIp, String deviceInfo
            , String signType) throws WxPayException {
        EntPayRequest entPayRequest = new EntPayRequest();
        entPayRequest.setOpenid(openid);
        entPayRequest.setDeviceInfo(deviceInfo);
        entPayRequest.setAmount(amount);
        entPayRequest.setCheckName(checkName);
        entPayRequest.setDescription(description);
        entPayRequest.setMchAppid(mchAppId);
        entPayRequest.setMchId(mchId);
        entPayRequest.setPartnerTradeNo(partnerTradeNo);
        entPayRequest.setSpbillCreateIp(spbillCreateIp);
        entPayRequest.setNonceStr(nonceStr);
        entPayRequest.setSign(sign);
        entPayRequest.setSignType(signType);
        entPayRequest.setReUserName(reUserName);

        EntPayService entPayService = this.wxPayService.getEntPayService();

        return entPayService.entPay(entPayRequest);
    }

    /**
     * 微信小程序企业付款
     *
     * @param partnerTradeNo 1 商户订单号，需保持唯一性
     * @param openid         1 商户appid下，某用户的openid
     * @param amount         1 企业付款金额，单位为 分
     * @param description    1 企业付款操作说明信息。必填。
     * @return
     * @throws WxPayException
     */
    public EntPayResult miniAppEntPay(String partnerTradeNo, String openid
            , Integer amount, String description) throws WxPayException {
        return this.entPay(null, null, null, null, partnerTradeNo, openid, "NO_CHECK", null, amount, description, clientIp, null, null);
    }


    /**
     * 发送微信红包
     *
     * @param nonceStr     1 随机字符串，不长于32位
     * @param sign         1 签名
     * @param mchBillNo    1 商户订单号
     * @param mchId        1 微信支付分配的商户号
     * @param wxAppId      1 微信分配的公众账号ID（企业号corpid即为此appId）。接口传入的所有appid应该为公众号的appid（在mp.weixin.qq.com申请的），不能为APP的appid（在open.weixin.qq.com申请的）。
     * @param sendName     1 红包发送者名称
     * @param reOpenID     1 接受红包的用户用户在wxappid下的openid
     * @param totalAmount  1 付款金额，单位 分
     * @param totalNum     1 红包发放总人数
     * @param wishing      1 红包祝福语
     * @param clientIp     1 调用接口的机器Ip地址
     * @param actName      1 活动名称
     * @param remark       1 备注信息
     * @param sceneId      0 发放红包使用场景，红包金额大于200时必传PRODUCT_1:商品促销PRODUCT_2:抽奖PRODUCT_3:虚拟物品兑奖PRODUCT_4:企业内部福利PRODUCT_5:渠道分润PRODUCT_6:保险回馈PRODUCT_7:彩票派奖PRODUCT_8:税务刮奖
     * @param riskInfo     0 活动信息
     * @param consumeMchId 0 资金授权商户号
     * @param amtType      0 裂变红包必填,红包金额设置方式ALL_RAND—全部随机,商户指定总金额和红包发放总人数，由微信支付随机计算出各红包金额
     * @return
     * @throws WxPayException
     */
    private WxPaySendRedpackResult sendRedpack(String nonceStr, String sign, String mchBillNo, String mchId, String wxAppId, String sendName
            , String reOpenID, Integer totalAmount, Integer totalNum, String wishing, String clientIp, String actName, String remark
            , String sceneId, String riskInfo, String consumeMchId, String amtType) throws WxPayException {
        WxPaySendRedpackRequest request = new WxPaySendRedpackRequest();
        request.setActName(actName);
        request.setAmtType(amtType);
        request.setClientIp(clientIp);
        request.setConsumeMchId(consumeMchId);
        request.setMchBillNo(mchBillNo);
        request.setRemark(remark);
        request.setReOpenid(reOpenID);
        request.setWxAppid(wxAppId);
        request.setWishing(wishing);
        request.setTotalNum(totalNum);
        request.setTotalAmount(totalAmount);
        request.setSendName(sendName);
        request.setSceneId(sceneId);
        request.setRiskInfo(riskInfo);
        request.setSign(sign);
        request.setNonceStr(nonceStr);
        request.setMchId(mchId);

        return wxPayService.sendRedpack(request);
    }

    /**
     * 微信小程序退款
     *
     * @param body        退款原因
     * @param outTradeNo  商户系统内部订单号
     * @param outRefundNo 商户系统退款订单号
     * @param totalFee    订单总金额，单位为 分
     * @param refundFee   退款金额，单位为 分
     * @return
     */
    public WxPayRefundResult miniAppRefund(String body, String outTradeNo, String outRefundNo, Integer totalFee, Integer refundFee) throws WxPayException {

        WxPayRefundRequest request = new WxPayRefundRequest();
        request.setRefundDesc(body);
        request.setOutTradeNo(outTradeNo);
        request.setOutRefundNo(outRefundNo);
        request.setTotalFee(totalFee);
        request.setRefundFee(refundFee);

        return wxPayService.refund(request);
    }


    /**
     * 查询订单接口
     *
     * @param rechargeId
     * @param transactionId
     * @return
     * @throws WxPayException
     */
    public WxPayOrderQueryResult queryOrder(Long rechargeId, String transactionId) throws WxPayException {
        return wxPayService.queryOrder(transactionId, rechargeId.toString());
    }

    /**
     * 关闭订单接口
     *
     * @param rechargeId
     * @return
     * @throws WxPayException
     */
    public WxPayOrderCloseResult closeOrder(Long rechargeId) throws WxPayException {
        return wxPayService.closeOrder(rechargeId.toString());
    }

}
